package com.denlaku.longan.service.impl;

import com.denlaku.longan.anno.RequiredTx;
import com.denlaku.longan.qo.DataSetQuery;
import com.denlaku.longan.qo.Query;
import com.denlaku.longan.repository.DataSetRepository;
import com.denlaku.longan.service.DataSetFieldService;
import com.denlaku.longan.service.DataSetService;
import com.denlaku.longan.service.DataOriginService;
import com.denlaku.longan.service.MetaDataService;
import com.denlaku.longan.service.PlaceholderService;
import com.denlaku.longan.util.SnowflakeUtils;
import com.denlaku.longan.vo.DataSet;
import com.denlaku.longan.vo.DataSetField;
import com.denlaku.longan.vo.DataOrigin;
import com.denlaku.longan.vo.MetaData;
import com.denlaku.longan.vo.Placeholder;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.jspecify.annotations.NonNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Function;

/**
 * @author tianx
 */
@Service
public class DataSetServiceImpl implements DataSetService {

	private static final ConcurrentMap<Long, Cache<@NonNull Long, DataSet>> CACHE = new ConcurrentHashMap<>();

	private static final Function<Long, Cache<@NonNull Long, DataSet>> TENANT_CACHE_FN = tenantId -> CACHE.computeIfAbsent(
		tenantId, k -> Caffeine.newBuilder().expireAfterWrite(Duration.ofDays(7)).build());

	@Autowired
	private DataSetRepository dataSetRepository;

	@Autowired
	private MetaDataService metaDataService;

	@Autowired
	private DataOriginService dataOriginService;

	@Autowired
	private DataSetFieldService dataSetFieldService;

	@Autowired
	private PlaceholderService placeholderService;

	@Override
	@RequiredTx
	public int add(DataSet dataSet) {
		int add = dataSetRepository.add(dataSet);
		addFields(dataSet);
		this.addPlaceholder(dataSet);
		this.addCache(dataSet);
		return add;
	}

	@Override
	@RequiredTx
	public int update(DataSet dataSet) {
		int update = dataSetRepository.update(dataSet);
		Long tenantId = dataSet.getTenantId();
		Long id = dataSet.getId();
		dataSetFieldService.delete(tenantId, id);
		placeholderService.delete(tenantId, id);
		this.addFields(dataSet);
		this.addPlaceholder(dataSet);
		this.updateCache(dataSet);
		return update;
	}

	private void addFields(DataSet dataSet) {
		Long dataSetId = dataSet.getId();
		List<DataSetField> fields = dataSet.getFields();
		if (fields == null || fields.isEmpty()) {
			return;
		}
		fields.forEach(field -> {
			field.setDataSetId(dataSetId);
			field.setTenantId(dataSet.getTenantId());
			field.setId(SnowflakeUtils.nextId());
			dataSetFieldService.add(field);
		});
	}

	private void addPlaceholder(DataSet dataSet) {
		List<Placeholder> placeholders = dataSet.getPlaceholders();
		if (placeholders == null || placeholders.isEmpty()) {
			return;
		}
		Long dataSetId = dataSet.getId();
		placeholders.forEach(placeholder -> {
			placeholder.setTenantId(dataSet.getTenantId());
			placeholder.setDataSetId(dataSetId);
			placeholderService.add(placeholder);
		});
	}

	@Override
	@RequiredTx
	public int delete(Query query) {
		int delete = dataSetRepository.delete(query);
		dataSetFieldService.delete(query.getTenantId(), query.getId());
		placeholderService.delete(query.getTenantId(), query.getId());
		return delete;
	}

	@Override
	public List<DataSet> list(DataSetQuery query) {
		return dataSetRepository.list(query);
	}

	@Override
	public DataSet get(Query query) {
		DataSet dataSet = dataSetRepository.get(query);
		if (dataSet == null) {
			return null;
		}
		List<DataSetField> fields = dataSetFieldService.list(query.getTenantId(), query.getId());
		dataSet.setFields(fields);
		List<Placeholder> placeholders = placeholderService.list(query.getTenantId(), query.getId());
		dataSet.setPlaceholders(placeholders);
		return dataSet;
	}

	@Override
	public DataSet getBase(Query query) {
		return dataSetRepository.get(query);
	}

	@Override
	@RequiredTx
	public void addCache(DataSet dataSet) {
		Long tenantId = dataSet.getTenantId();
		Cache<@NonNull Long, DataSet> cache = TENANT_CACHE_FN.apply(tenantId);
		cache.put(dataSet.getId(), dataSet);
	}

	@Override
	@RequiredTx
	public void updateCache(DataSet dataSet) {
		this.addCache(dataSet);
	}

	@Override
	@RequiredTx
	public void deleteCache(Query query) {
		Long tenantId = query.getTenantId();
		Cache<@NonNull Long, DataSet> cache = TENANT_CACHE_FN.apply(tenantId);
		cache.invalidate(query.getId());
	}

	@Override
	@RequiredTx
	public DataSet getCache(Query query) {
		Long tenantId = query.getTenantId();
		Cache<@NonNull Long, DataSet> cache = TENANT_CACHE_FN.apply(tenantId);
		return cache.get(query.getId(), k -> this.get(query));
	}

	@Override
	public List<MetaData> listMetaData(DataSet dataSet) {
		Long dataOriginId = dataSet.getDataOriginId();
		DataOrigin dataOrigin = dataOriginService.get(Query.of(dataOriginId));
		String password = dataOriginService.getPassword(Query.of(dataOriginId));
        dataOrigin.setPassword(password);
		return metaDataService.list(dataOrigin, dataSet.getCommand());
	}

	@Override
	public void clearTag(Long tenantId, Long tagId) {
		dataSetRepository.clearTag(tenantId, tagId);
	}

}
